package com.hww.common.util;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;

import androidx.annotation.RequiresPermission;

import com.hww.common.MyAppThis;
import com.hww.common.util.tool.IPAddressUtil;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class NetworkUtils {

    private NetworkUtils() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    public enum NetworkType {
        /**
         * 以太网
         */
        NETWORK_ETHERNET,
        NETWORK_WIFI,
        NETWORK_5G,
        NETWORK_4G,
        NETWORK_3G,
        NETWORK_2G,
        NETWORK_UNKNOWN,
        NETWORK_NO
    }


    /**
     * 打开WIFI设置界面
     */
    public static void openWifiSettings() {
        MyAppThis.getThis().startActivity(
                new Intent(Settings.ACTION_WIFI_SETTINGS)
                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        );
    }

    /**
     * 打开无线设置界面--（无线和网络设置界面）
     */
    public static void openWirelessSettings() {
        MyAppThis.getThis().startActivity(
                new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS)
                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        );
    }

    /**
     * 判断当前设备是否连接到网络。
     * <p>
     * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}
     * </p>
     *
     * @return 如果设备已连接到网络则返回true，否则返回false。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    public static boolean isConnected() {
        NetworkInfo info = getActiveNetworkInfo();
        /*isConnected()	检查设备是否已建立有效网络连接，且能传输数据*/
        /*isAvailable()	检查网络是否处于可连接状态，与当前是否实际连接无关*/
        return info != null && info.isConnected() && info.isAvailable();
    }


    /**
     * 此函数用于测试到指定主机和端口的Socket连接是否可用(默认超时时间三秒)。
     *
     * @param host 主机名或IP地址，目标服务器的位置。
     * @param port 端口号，目标服务器上要连接的服务所监听的端口。
     * @return 如果在指定时间内成功连接到主机，返回true；否则，由于超时或异常，返回false。
     */
    public static boolean testSocket(String host, int port) {
        return testSocket(host, port, 3 * 1000);
    }

    /**
     * 此函数用于测试到指定主机和端口的Socket连接是否可用。
     * 在给定的超时时间内，如果能够成功建立连接，则返回true；否则返回false。
     *
     * @param host    主机名或IP地址，目标服务器的位置。
     * @param port    端口号，目标服务器上要连接的服务所监听的端口。
     * @param timeout 超时值，以毫秒为单位，超过此时间仍未建立连接则抛出异常。
     * @return 如果在指定时间内成功连接到主机，返回true；否则，由于超时或异常，返回false。
     */
    public static boolean testSocket(String host, int port, int timeout) {
        Socket socket = new Socket();
        try {
            socket.connect(new InetSocketAddress(host, port), timeout);
            return true;
        } catch (SocketTimeoutException e) {
            // 连接超时
            return false;
        } catch (IOException e) {
            // 连接I/O异常，如网络问题
            return false;
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                // 关闭连接异常
            }
        }
    }


    /**
     * 通过ping命令检查网络是否可用。
     * <p>
     * 该方法通过执行ping命令向指定的IP地址发送一个ICMP请求，然后根据返回的结果判断网络是否可用。
     * 如果指定的IP地址为空或不合法，则会使用一个默认的IP地址进行测试。
     * <p>需要有权限 {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
     * </p>
     *
     * @param ip 待测试的IP地址。如果为null或不合法，则会使用默认IP地址进行测试。
     * @return 如果网络可用且ping命令执行成功，则返回true；否则返回false。
     */
    @RequiresPermission(android.Manifest.permission.INTERNET)
    public static boolean isAvailableByPing(String ip) {
        if (ip == null || ip.length() <= 0) {
            ip = "223.5.5.5";// default ping ip
        }
        ShellUtils.CommandResult result = ShellUtils.execCommand(String.format("ping -c 2 %s", ip), false);
        boolean ret = result.result == 0;
        if (result.errorMsg != null) {
            Log.d("NetworkUtils", "isAvailableByPing() called" + result.errorMsg);
        }
        if (result.successMsg != null) {
            Log.d("NetworkUtils", "isAvailableByPing() called" + result.successMsg);
        }
        return ret;
    }

    /**
     * 通过ping命令检查网络是否可用
     *
     * @param ip       待测试的IP地址
     * @param consumer 接收内容的接口, 如果ping命令执行成功，则接收到成功信息，否则接收到错误信息
     */
    @RequiresPermission(android.Manifest.permission.INTERNET)
    public static void isAvailableByPing(String ip, Consumer<String> consumer) {
        if (null == consumer) {
            return;
        }
        String pingCommand = String.format("ping -c 5 %s", ip);
        /* 用于执行命令的进程。 */
        Process process = null;
        /* 用于读取命令执行成功输出的BufferedReader。 */
        BufferedReader successResult = null;

        /* 用于向进程发送命令的DataOutputStream。 */
        DataOutputStream os = null;
        try {
            /* 根据是否以root权限执行，选择执行命令的shell */
            process = Runtime.getRuntime().exec(ShellUtils.COMMAND_SH);
            os = new DataOutputStream(process.getOutputStream());
            /*将命令以字节形式写入输出流，避免字符集错误*/
            os.write(pingCommand.getBytes());
            /*在命令后添加换行符，表示命令结束*/
            os.writeBytes(ShellUtils.COMMAND_LINE_END);
            /*刷新输出流，确保命令被立即发送*/
            os.flush();
            /*发送退出命令，结束进程*/
            os.writeBytes(ShellUtils.COMMAND_EXIT);
            os.flush();

            /*待进程执行完毕，获取进程的退出码*/
            int result = process.waitFor();
            if (0 == result) {
                consumer.accept("ping success");
            } else {
                consumer.accept("ping fail");
            }
            successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String s;
            while ((s = successResult.readLine()) != null) {
                consumer.accept(s);
            }
        } catch (Exception e) {
            consumer.accept(e.getMessage());
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (successResult != null) {
                    successResult.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (process != null) {
                process.destroy();
            }
        }
    }

    /**
     * 判断移动数据是否开启。
     * 通过调用TelephonyManager的私有方法getDataEnabled获取移动数据状态。
     * 注意：此方法依赖于反射机制来调用TelephonyManager的私有方法，可能在未来的Android版本中不再有效。
     *
     * @return 如果移动数据开启返回true，否则返回false。
     */
    public static boolean getMobileDataEnabled() {
        try {
            TelephonyManager tm =
                    (TelephonyManager) MyAppThis.getThis().getSystemService(Context.TELEPHONY_SERVICE);
            if (tm == null) {
                return false;
            }
            @SuppressLint("PrivateApi")
            Method getMobileDataEnabledMethod = tm.getClass().getDeclaredMethod("getDataEnabled");
            if (null != getMobileDataEnabledMethod) {
                return (boolean) getMobileDataEnabledMethod.invoke(tm);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 判断当前系统是否配置了HTTP代理。
     * <p>
     * 本方法通过检查系统属性中的http.proxyHost和http.proxyPort来确定系统是否配置了HTTP代理。
     * 如果这两个属性之一为空，表示系统没有配置HTTP代理，即当前不通过代理访问网络。
     * 如果两个属性都不为空，则表示系统配置了HTTP代理，可能通过代理访问网络。
     * <p>
     * 注意：这种方法只能检测到系统级的HTTP代理配置，对于其他类型的代理（如HTTPS、SOCKS）或应用程序级的代理配置不敏感。
     *
     * @return 如果系统配置了HTTP代理，则返回true；否则返回false。
     */
    public static boolean isBehindProxy() {
        /*检查http.proxyHost和http.proxyPort系统属性是否存在，如果都存在则表示可能通过代理访问网络*/
        return !(System.getProperty("http.proxyHost") == null || System.getProperty("http.proxyPort") == null);
    }

    /**
     * 通过DNS查询检查网络是否可用。
     * <p>
     * <p>需要权限 {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
     * 注意：此方法的网络检测逻辑比较简单，只能作为网络可用性的初步判断。在某些情况下，即使DNS查询成功，
     * 也可能存在网络连接问题。
     * <p>
     *
     * @param domain 指定的域名，用于DNS查询。如果为null或不合法，将使用默认域名“www.baidu.com”。
     * @return 如果DNS查询成功，返回true，表示网络可用；如果DNS查询失败或抛出异常，返回false，表示网络不可用。
     */
    @RequiresPermission(android.Manifest.permission.INTERNET)
    public static boolean isAvailableByDns(final String domain) {
        final String realDomain = StringUtils.isEmpty(domain) ? "www.baidu.com" : domain;
        InetAddress inetAddress;
        try {
            inetAddress = InetAddress.getByName(realDomain);
            return inetAddress != null;
        } catch (UnknownHostException e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 获取本地DNS
     *
     * @param dns DNS名称 (getprop net.dns1  getprop net.dns2)
     * @return
     */
    public static String getLocalDns(String dns) {
        Process process = null;
        String str = "";
        BufferedReader reader = null;
        try {
            if (null == dns || dns.isEmpty()) {
                dns = "dns1";
            }
            process = Runtime.getRuntime().exec("getprop net." + dns);
            reader = new BufferedReader(new InputStreamReader(
                    process.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                str += line;
            }
            reader.close();
            process.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
                process.destroy();
            } catch (Exception e) {
            }
        }
        return str.trim();
    }


    /**
     * 判断当前设备是否正在使用VPN网络。
     * <p>
     * 本方法通过查询ConnectivityManager来确定当前网络连接类型是否为VPN。针对不同的Android系统版本，
     * 使用不同的方法来获取VPN网络信息，以兼容Android P及以下版本的系统。
     *
     * @return 如果当前设备正在使用VPN网络，则返回true；否则返回false。
     */
    public static boolean isUsingVPN() {
        ConnectivityManager cm = (ConnectivityManager) MyAppThis.getThis().getSystemService(Context.CONNECTIVITY_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            return cm.getNetworkInfo(ConnectivityManager.TYPE_VPN).isConnectedOrConnecting();
        } else {
            return cm.getNetworkInfo(NetworkCapabilities.TRANSPORT_VPN).isConnectedOrConnecting();
        }
    }


    /**
     * 设置移动数据的开启状态。
     * 此方法用于直接调用TelephonyManager的私有方法setDataEnabled来开启或关闭移动数据。
     * 需要MODIFY_PHONE_STATE权限来执行此操作。
     *
     * @param enabled true表示开启移动数据，false表示关闭移动数据。
     */
    @SuppressLint("SupportAnnotationUsage")
    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
    public static void setMobileDataEnabled(final boolean enabled) {
        try {
            TelephonyManager tm =
                    (TelephonyManager) MyAppThis.getThis().getSystemService(Context.TELEPHONY_SERVICE);
            if (tm == null) {
                return;
            }
            Method setMobileDataEnabledMethod =
                    tm.getClass().getDeclaredMethod("setDataEnabled", boolean.class);
            if (null != setMobileDataEnabledMethod) {
                setMobileDataEnabledMethod.invoke(tm, enabled);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 判断是否使用移动数据
     * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
     *
     * @return {@code true}: 使用移动数据<br>{@code false}: 不是
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    public static boolean isMobileData() {
        NetworkInfo info = getActiveNetworkInfo();
        return null != info
                && info.isAvailable()
                && info.getType() == ConnectivityManager.TYPE_MOBILE;
    }

    /**
     * 判断是否使用的4G网络
     * <p>Must hold
     * {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
     *
     * @return {@code true}: yes<br>{@code false}: no
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    public static boolean is4G() {
        NetworkInfo info = getActiveNetworkInfo();
        return info != null
                && info.isAvailable()
                && info.getSubtype() == TelephonyManager.NETWORK_TYPE_LTE;
    }

    /**
     * 判断是否使用的5G网络
     *
     * @return
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    public static boolean is5G() {
        NetworkInfo info = getActiveNetworkInfo();
        return info != null
                && info.isAvailable()
                && info.getSubtype() == TelephonyManager.NETWORK_TYPE_NR;
    }

    /**
     * 判断wifi是否开启
     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />}</p>
     *
     * @return {@code true}: wifi开启<br>{@code false}: 未开启
     */
    @SuppressLint("SupportAnnotationUsage")
    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
    public static boolean getWifiEnabled() {
        @SuppressLint("WifiManagerLeak")
        WifiManager manager = (WifiManager) MyAppThis.getThis().getSystemService(android.content.Context.WIFI_SERVICE);
        return manager != null && manager.isWifiEnabled();
    }

    /**
     * 设置wifi开启状态
     * <p>Must hold {@code <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />}</p>
     * <p> {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />}</p>
     *
     * @param enabled True 打开, false 关闭.
     */
    @RequiresPermission(allOf = {android.Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_WIFI_STATE})
    public static void setWifiEnabled(final boolean enabled) {
        @SuppressLint("WifiManagerLeak")
        WifiManager manager = (WifiManager) MyAppThis.getThis().getSystemService(android.content.Context.WIFI_SERVICE);
        if (manager == null || enabled == manager.isWifiEnabled()) {
            return;
        }
        manager.setWifiEnabled(enabled);
    }

    /**
     * 检查当前设备是否通过Wi-Fi连接到网络。
     *
     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
     *
     * @return 如果设备通过Wi-Fi连接到网络，则返回true；否则返回false。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    public static boolean isWifiConnected() {
        ConnectivityManager cm =
                (ConnectivityManager) MyAppThis.getThis().getSystemService(Context.CONNECTIVITY_SERVICE);
        return cm != null
                && cm.getActiveNetworkInfo() != null
                && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI;
    }


    /**
     * 获取当前设备的网络运营商名称。
     * 通过TelephonyManager获取网络运营商信息，如果TelephonyManager为null或无法获取运营商名称，则返回空字符串。
     *
     * @return 当前设备的网络运营商名称，如果无法获取则返回空字符串。
     */
    public static String getNetworkOperatorName() {
        TelephonyManager tm =
                (TelephonyManager) MyAppThis.getThis().getSystemService(Context.TELEPHONY_SERVICE);
        return tm != null ? tm.getNetworkOperatorName() : "";
    }

    /**
     * 获取当前网络连接的类型
     * <p>Must hold {@code <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />}</p>
     *
     * @return 当前网络连接的类型，具体类型参见NetworkType枚举
     * <ul>
     * <li>{@link NetworkUtils.NetworkType#NETWORK_ETHERNET} </li>
     * <li>{@link NetworkUtils.NetworkType#NETWORK_WIFI    } </li>
     * <li>{@link NetworkUtils.NetworkType#NETWORK_5G      } </li>
     * <li>{@link NetworkUtils.NetworkType#NETWORK_4G      } </li>
     * <li>{@link NetworkUtils.NetworkType#NETWORK_3G      } </li>
     * <li>{@link NetworkUtils.NetworkType#NETWORK_2G      } </li>
     * <li>{@link NetworkUtils.NetworkType#NETWORK_UNKNOWN } </li>
     * <li>{@link NetworkUtils.NetworkType#NETWORK_NO      } </li>
     * </ul>
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    public static NetworkType getNetworkType() {
        NetworkType netType = NetworkType.NETWORK_NO;
        NetworkInfo info = getActiveNetworkInfo();
        if (info != null && info.isAvailable()) {
            if (info.getType() == ConnectivityManager.TYPE_ETHERNET) {
                netType = NetworkType.NETWORK_ETHERNET;
            } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
                netType = NetworkType.NETWORK_WIFI;
            } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
                switch (info.getSubtype()) {
                    /* 判断为2G网络类型的子类型 */
                    case TelephonyManager.NETWORK_TYPE_GSM:
                    case TelephonyManager.NETWORK_TYPE_GPRS:
                    case TelephonyManager.NETWORK_TYPE_CDMA:
                    case TelephonyManager.NETWORK_TYPE_EDGE:
                    case TelephonyManager.NETWORK_TYPE_1xRTT:
                    case TelephonyManager.NETWORK_TYPE_IDEN:
                        netType = NetworkType.NETWORK_2G;
                        break;
                    /*判断为3G网络类型的子类型*/
                    case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
                    case TelephonyManager.NETWORK_TYPE_EVDO_A:
                    case TelephonyManager.NETWORK_TYPE_UMTS:
                    case TelephonyManager.NETWORK_TYPE_EVDO_0:
                    case TelephonyManager.NETWORK_TYPE_HSDPA:
                    case TelephonyManager.NETWORK_TYPE_HSUPA:
                    case TelephonyManager.NETWORK_TYPE_HSPA:
                    case TelephonyManager.NETWORK_TYPE_EVDO_B:
                    case TelephonyManager.NETWORK_TYPE_EHRPD:
                    case TelephonyManager.NETWORK_TYPE_HSPAP:
                        netType = NetworkType.NETWORK_3G;
                        break;

                    /*判断为4G网络类型的子类型*/
                    case TelephonyManager.NETWORK_TYPE_IWLAN:
                    case TelephonyManager.NETWORK_TYPE_LTE:
                        netType = NetworkType.NETWORK_4G;
                        break;
                    /*判断为5G网络类型的子类型*/
                    case TelephonyManager.NETWORK_TYPE_NR:
                        netType = NetworkType.NETWORK_5G;
                        break;
                    default:
                        /*对于未知的移动网络子类型，通过子类型名称进一步判断*/
                        String subtypeName = info.getSubtypeName();
                        if (subtypeName.equalsIgnoreCase("TD-SCDMA")
                                || subtypeName.equalsIgnoreCase("WCDMA")
                                || subtypeName.equalsIgnoreCase("CDMA2000")) {
                            netType = NetworkType.NETWORK_3G;
                        } else {
                            netType = NetworkType.NETWORK_UNKNOWN;
                        }
                        break;
                }
            } else {
                netType = NetworkType.NETWORK_UNKNOWN;
            }
        }
        return netType;
    }

    /**
     * 获取当前活动的网络信息。
     * <p>
     * 本方法用于检查当前设备的网络连接状态，以确定是否可以进行网络操作。
     * 它首先通过获取ConnectivityManager实例来管理网络连接的状态。
     * 如果ConnectivityManager可用，则尝试获取当前活动的网络信息。
     * 如果ConnectivityManager不可用或没有活动的网络连接，则返回null。
     *
     * @return NetworkInfo 对象，表示当前活动的网络连接信息，如果没有任何网络连接或无法获取信息，则返回null。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    private static NetworkInfo getActiveNetworkInfo() {
        ConnectivityManager manager =
                (ConnectivityManager) MyAppThis.getThis().getSystemService(Context.CONNECTIVITY_SERVICE);
        if (manager == null) {
            return null;
        }
        return manager.getActiveNetworkInfo();
    }

    /**
     * 获取当前设备的IP地址。
     * 根据参数决定返回IPv4还是IPv6的地址。
     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
     *
     * @param useIPv4 如果为true，则优先返回IPv4地址；如果为false，则优先返回IPv6地址。
     * @return 当前设备的IP地址字符串，如果无法获取则返回空字符串。
     */
    @RequiresPermission(android.Manifest.permission.INTERNET)
    public static String getIPAddress(final boolean useIPv4) {
        try {
            Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
            LinkedList<InetAddress> adds = new LinkedList<>();
            while (nis.hasMoreElements()) {
                NetworkInterface ni = nis.nextElement();
                // To prevent phone of xiaomi return "10.0.2.15"
                if (!ni.isUp() || ni.isLoopback()) {
                    continue;
                }
                Enumeration<InetAddress> addresses = ni.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    adds.addFirst(addresses.nextElement());
                }
            }
            for (InetAddress add : adds) {
                if (!add.isLoopbackAddress()) {
                    String hostAddress = add.getHostAddress();
                    boolean isIPv4 = hostAddress.indexOf(':') < 0;
                    if (useIPv4) {
                        if (isIPv4) {
                            return hostAddress;
                        }
                    } else {
                        if (!isIPv4) {
                            int index = hostAddress.indexOf('%');
                            return index < 0
                                    ? hostAddress.toUpperCase()
                                    : hostAddress.substring(0, index).toUpperCase();
                        }
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 根据域名获取对应的IP地址(www.baidu.com --> 183.2.172.185)。
     * <p>Must hold {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
     * 本方法通过解析域名来获取对应的IP地址，需要网络权限才能正常运行。
     * 如果域名解析失败，将返回空字符串。
     *
     * @param domain 域名，例如"www.example.com"。
     * @return 对应的IP地址字符串，解析失败时返回空字符串。
     */
    @RequiresPermission(android.Manifest.permission.INTERNET)
    public static String getDomainAddress(final String domain) {
        InetAddress inetAddress;
        try {
            inetAddress = InetAddress.getByName(domain);
            return inetAddress.getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
            return "";
        }
    }


    /**
     * 获取当前WiFi连接的IP地址。
     * <p>
     * 本方法通过调用系统服务获取WiFi管理器实例，进而获取DHCP信息中分配给设备的IP地址。
     * 使用了@RequiresPermission注解来强调需要ACCESS_WIFI_STATE权限，否则无法执行该操作。
     * 使用SuppressLint注解来忽略“潜在的泄漏”警告，因为这里对WifiManager的使用是符合场景的。
     *
     * @return 当前WiFi连接的IP地址，如果无法获取或WiFi未开启，则返回空字符串。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
    public static String getIpAddressByWifi() {
        @SuppressLint("WifiManagerLeak")
        WifiManager wm = (WifiManager) MyAppThis.getThis().getSystemService(android.content.Context.WIFI_SERVICE);
        if (wm == null) return "";
        return Formatter.formatIpAddress(wm.getDhcpInfo().ipAddress);
    }

    /**
     * 通过WiFi获取网关地址。
     * <p>
     * 此方法需要ACCESS_WIFI_STATE权限来访问WiFi管理器并获取网关信息。
     * 使用WifiManager的getDhcpInfo方法来获取DHCP信息，其中包括网关的IP地址。
     * 返回格式化的网关IP地址字符串，如果WifiManager实例获取失败，则返回空字符串。
     *
     * @return 网关的IP地址字符串，如果无法获取则返回空字符串。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
    public static String getGatewayByWifi() {
        @SuppressLint("WifiManagerLeak")
        WifiManager wm = (WifiManager) MyAppThis.getThis().getSystemService(android.content.Context.WIFI_SERVICE);
        if (wm == null) {
            return "";
        }
        return Formatter.formatIpAddress(wm.getDhcpInfo().gateway);
    }

    /**
     * 获取当前Wi-Fi连接的子网掩码。
     * <p>
     * 本方法通过访问Wi-Fi管理器，获取当前Wi-Fi连接的DHCP信息，从而得到子网掩码。
     * 使用了@RequiresPermission注解来表明需要ACCESS_WIFI_STATE权限才能调用。
     *
     * @return 当前Wi-Fi连接的子网掩码，如果无法获取或Wi-Fi管理器为null，则返回空字符串。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
    public static String getNetMaskByWifi() {
        /*忽略WifiManager单例泄露的警告，因为这是一个静态方法，上下文是应用程序本身。*/
        @SuppressLint("WifiManagerLeak")
        WifiManager wm = (WifiManager) MyAppThis.getThis().getSystemService(android.content.Context.WIFI_SERVICE);
        if (wm == null) return "";
        /*格式化IP地址，将获取到的子网掩码以字符串形式返回*/
        return Formatter.formatIpAddress(wm.getDhcpInfo().netmask);
    }

    /**
     * 通过Wi-Fi获取服务器地址。
     * <p>
     * 此方法用于获取当前Wi-Fi连接的DHCP服务器分配的IP地址，以便于与服务器进行网络通信。
     * 需要ACCESS_WIFI_STATE权限来访问Wi-Fi状态。
     *
     * @return 服务器的IP地址字符串，如果无法获取则返回空字符串。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
    public static String getServerAddressByWifi() {
        @SuppressLint("WifiManagerLeak")
        WifiManager wm = (WifiManager) MyAppThis.getThis().getSystemService(android.content.Context.WIFI_SERVICE);
        if (wm == null) return "";
        return Formatter.formatIpAddress(wm.getDhcpInfo().serverAddress);
    }

    /**
     * 获取当前连接的WiFi的SSID。
     *
     * @return 当前连接的WiFi的SSID。如果无法获取WiFi信息或SSID为空，则返回空字符串。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
    public static String getSSID() {
        WifiManager wm = (WifiManager) MyAppThis.getThis().getApplicationContext().getSystemService(android.content.Context.WIFI_SERVICE);
        if (wm == null) {
            return "";
        }
        WifiInfo wi = wm.getConnectionInfo();
        if (wi == null) {
            return "";
        }
        String ssid = wi.getSSID();
        if (TextUtils.isEmpty(ssid)) {
            return "";
        }
        if (ssid.length() > 2 && ssid.charAt(0) == '"' && ssid.charAt(ssid.length() - 1) == '"') {
            return ssid.substring(1, ssid.length() - 1);
        }
        return ssid;
    }


    /**
     * 域名解析
     *
     * @param domain 域名
     * @return
     */
    public static Map<String, Object> getDomainIp(String domain) {
        Map<String, Object> map = new HashMap<>();
        long start = 0;
        long end = 0;
        String time = null;
        InetAddress[] remoteInet = null;
        try {
            start = System.currentTimeMillis();
            remoteInet = InetAddress.getAllByName(domain);
            if (remoteInet != null) {
                end = System.currentTimeMillis();
                time = (end - start) + "";
            }
        } catch (UnknownHostException e) {
            end = System.currentTimeMillis();
            time = (end - start) + "";
            remoteInet = null;
            e.printStackTrace();
        } finally {
            map.put("remoteInet", remoteInet);
            map.put("useTime", time);
        }
        return map;
    }

    /**
     * 检验IPv4地址是否合法
     *
     * @param ip ip地址
     * @return true：合法，false：非法
     */
    public static boolean isValidIPv4WithRegex(String ip) {
        String regex = "^((\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])$";
//        String regex = "^((25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})$";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(ip);
        return matcher.matches();
    }


    /**
     * 判断给定的IP地址是否为内部IP地址(局域网)。
     * 内部IP地址指的是不直接面向互联网的IP地址，如私有IP地址。
     *
     * @param ip 字符串形式的IP地址。
     * @return 如果IP地址是内部IP，则返回true；否则返回false。
     */
    public boolean internalIp(String ip) {
        byte[] addr = IPAddressUtil.textToNumericFormatV4(ip);
        return internalIpByByte(addr);
    }

    /**
     * 此函数将字节数组转换为IP地址，并检查它是否属于私有内部IP地址范围。
     * 私有IP地址范围包括：
     * - 10.0.0.0/8
     * - 172.16.0.0/12
     * - 192.168.0.0/16
     *
     * @param addr 字节数组，代表IP地址的四个八位字节
     * @return 如果IP地址属于私有内部IP范围，返回true；否则返回false
     */
    public boolean internalIpByByte(byte[] addr) {
        final byte b0 = addr[0];
        final byte b1 = addr[1];
        //10.x.x.x/8
        final byte SECTION_1 = 0x0A;
        //172.16.x.x/12
        final byte SECTION_2 = (byte) 0xAC;
        final byte SECTION_3 = (byte) 0x10;
        final byte SECTION_4 = (byte) 0x1F;
        //192.168.x.x/16
        final byte SECTION_5 = (byte) 0xC0;
        final byte SECTION_6 = (byte) 0xA8;
        switch (b0) {
            case SECTION_1:
                return true;
            case SECTION_2:
                if (b1 >= SECTION_3 && b1 <= SECTION_4) {
                    return true;
                }
            case SECTION_5:
                switch (b1) {
                    case SECTION_6:
                        return true;
                }
            default:
                return false;

        }
    }


    /**
     * 注册网络状态变化监听器。
     * <p>
     * 本方法用于订阅网络状态变化事件。当设备的网络状态发生变化时，注册的监听器将被回调。
     * 请注意，使用本方法需要在应用的Manifest文件中声明ACCESS_NETWORK_STATE权限。
     * </p>
     *
     * @param listener 网络状态变化监听器。当网络状态发生变化时，此监听器的onNetworkStatusChanged方法将被调用。
     */
    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    public static void registerNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {
        NetworkChangedReceiver.getInstance().registerListener(listener);
    }

    /**
     * 判断指定的监听器是否已注册。
     *
     * @param listener 网络状态监听器
     * @return true 已注册, false 未注册.
     */
    public static boolean isRegisteredNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {
        return NetworkChangedReceiver.getInstance().isRegistered(listener);
    }

    /**
     * 取消注册网络状态改变监听器。
     * <p>
     * 当不再需要监听网络状态变化时，可以通过此方法取消注册之前注册的监听器。这有助于减少不必要的资源占用和回调。
     *
     * @param listener 要取消注册的网络状态改变监听器。传入的监听器对象应该是之前通过registerNetworkStatusChangedListener方法注册的。
     */
    public static void unregisterNetworkStatusChangedListener(final OnNetworkStatusChangedListener listener) {
        NetworkChangedReceiver.getInstance().unregisterListener(listener);
    }

    /**
     * 获取WiFi扫描结果。
     * 需要ACCESS_WIFI_STATE和ACCESS_COARSE_LOCATION权限来访问WiFi状态和进行位置定位。
     *
     * @return WifiScanResults对象，包含WiFi扫描结果。如果WiFi未开启或获取扫描结果失败，则返回一个空的WifiScanResults对象。
     */
    @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION})
    public static WifiScanResults getWifiScanResult() {
        WifiScanResults result = new WifiScanResults();
        if (!getWifiEnabled()) {
            return result;
        }
        @SuppressLint("WifiManagerLeak")
        WifiManager wm = (WifiManager) MyAppThis.getThis().getSystemService(android.content.Context.WIFI_SERVICE);
        //noinspection ConstantConditions
        List<ScanResult> results = wm.getScanResults();
        if (results != null) {
            result.setAllResults(results);
        }
        return result;
    }

    /**
     * 消费型接口，用于处理Wifi扫描结果。
     *
     * @param <T> 泛型参数，表示Wifi扫描结果(这里代表 WifiScanResults )。
     */
    public interface Consumer<T> {
        void accept(T t);
    }


    /**
     * wifi扫码扫描周期时间
     */
    private static final long SCAN_PERIOD_MILLIS = 3000;
    private static final Set<Consumer<WifiScanResults>> SCAN_RESULT_CONSUMERS = new CopyOnWriteArraySet<>();
    private static Timer sScanWifiTimer;
    private static WifiScanResults sPreWifiScanResults;

    @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION})
    public static void addOnWifiChangedConsumer(final Consumer<WifiScanResults> consumer) {
        if (consumer == null) {
            return;
        }
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (SCAN_RESULT_CONSUMERS.isEmpty()) {
                    SCAN_RESULT_CONSUMERS.add(consumer);
                    startScanWifi();
                    return;
                }
                consumer.accept(sPreWifiScanResults);
                SCAN_RESULT_CONSUMERS.add(consumer);
            }
        });
    }

    private static void startScanWifi() {
        sPreWifiScanResults = new WifiScanResults();
        sScanWifiTimer = new Timer();
        sScanWifiTimer.schedule(new TimerTask() {
            @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION})
            @Override
            public void run() {
                startScanWifiIfEnabled();
                WifiScanResults scanResults = getWifiScanResult();
                if (isSameScanResults(sPreWifiScanResults.allResults, scanResults.allResults)) {
                    return;
                }
                sPreWifiScanResults = scanResults;
                ThreadUtils.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        for (Consumer<WifiScanResults> consumer : SCAN_RESULT_CONSUMERS) {
                            consumer.accept(sPreWifiScanResults);
                        }
                    }
                });
            }
        }, 0, SCAN_PERIOD_MILLIS);
    }

    @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE})
    private static void startScanWifiIfEnabled() {
        if (!getWifiEnabled()) {
            return;
        }
        @SuppressLint("WifiManagerLeak")
        WifiManager wm = (WifiManager) MyAppThis.getThis().getSystemService(android.content.Context.WIFI_SERVICE);
        //noinspection ConstantConditions
        wm.startScan();
    }

    public static void removeOnWifiChangedConsumer(final Consumer<WifiScanResults> consumer) {
        if (consumer == null) {
            return;
        }
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                SCAN_RESULT_CONSUMERS.remove(consumer);
                if (SCAN_RESULT_CONSUMERS.isEmpty()) {
                    stopScanWifi();
                }
            }
        });
    }

    private static void stopScanWifi() {
        if (sScanWifiTimer != null) {
            sScanWifiTimer.cancel();
            sScanWifiTimer = null;
        }
    }

    private static boolean isSameScanResults(List<ScanResult> l1, List<ScanResult> l2) {
        if (l1 == null && l2 == null) {
            return true;
        }
        if (l1 == null || l2 == null) {
            return false;
        }
        if (l1.size() != l2.size()) {
            return false;
        }
        for (int i = 0; i < l1.size(); i++) {
            ScanResult r1 = l1.get(i);
            ScanResult r2 = l2.get(i);
            if (!isSameScanResultContent(r1, r2)) {
                return false;
            }
        }
        return true;
    }

    private static boolean isSameScanResultContent(ScanResult r1, ScanResult r2) {
        return r1 != null && r2 != null && StringUtils.equals(r1.BSSID, r2.BSSID)
                && StringUtils.equals(r1.SSID, r2.SSID)
                && StringUtils.equals(r1.capabilities, r2.capabilities)
                && r1.level == r2.level;
    }

    public static final class NetworkChangedReceiver extends BroadcastReceiver {

        private static NetworkChangedReceiver getInstance() {
            return LazyHolder.INSTANCE;
        }

        private NetworkType mType;
        private Set<OnNetworkStatusChangedListener> mListeners = new HashSet<>();

        /**
         * 注册监听器
         *
         * @param listener 监听器
         */
        @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
        void registerListener(final OnNetworkStatusChangedListener listener) {
            if (listener == null) {
                return;
            }
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
                public void run() {
                    int preSize = mListeners.size();
                    mListeners.add(listener);
                    if (preSize == 0 && mListeners.size() == 1) {
                        mType = getNetworkType();
                        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
                        MyAppThis.getThis().registerReceiver(NetworkChangedReceiver.getInstance(), intentFilter);
                    }
                }
            });
        }

        /**
         * 判断指定监听器是否注册
         *
         * @param listener 监听器
         * @return 是否注册
         */
        boolean isRegistered(final OnNetworkStatusChangedListener listener) {
            if (listener == null) {
                return false;
            }
            return mListeners.contains(listener);
        }

        /**
         * 取消注册
         *
         * @param listener
         */
        void unregisterListener(final OnNetworkStatusChangedListener listener) {
            if (listener == null) {
                return;
            }
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    int preSize = mListeners.size();
                    mListeners.remove(listener);
                    if (preSize == 1 && mListeners.size() == 0) {
                        MyAppThis.getThis().unregisterReceiver(NetworkChangedReceiver.getInstance());
                    }
                }
            });
        }

        /**
         * 每当发送到应用程序的广播与 BroadcastReceiver 注册的过滤条件匹配时，系统会调用 BroadcastReceiver 的 onReceive() 方法
         *
         * @param context 广播接收器当前运行的上下文环境。通过该上下文可以访问应用程序的资源和执行操作
         * @param intent  封装了广播消息的 Intent 对象。通过这个对象可以获取广播消息的详细信息，比如操作的动作、数据等
         */
        @Override
        public void onReceive(Context context, Intent intent) {
            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
                // debouncing
                ThreadUtils.runOnUiThreadDelayed(new Runnable() {
                    @Override
                    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
                    public void run() {
                        NetworkType networkType = NetworkUtils.getNetworkType();
                        if (mType == networkType) {
                            return;
                        }
                        mType = networkType;
                        if (networkType == NetworkType.NETWORK_NO) {
                            for (OnNetworkStatusChangedListener listener : mListeners) {
                                listener.onDisconnected();
                            }
                        } else {
                            for (OnNetworkStatusChangedListener listener : mListeners) {
                                listener.onConnected(networkType);
                            }
                        }
                    }
                }, 1000);
            }
        }

        private static class LazyHolder {
            private static final NetworkChangedReceiver INSTANCE = new NetworkChangedReceiver();
        }
    }


    public interface OnNetworkStatusChangedListener {
        /**
         * 网络断开
         */
        void onDisconnected();

        /**
         * 网络连接
         *
         * @param networkType 网络类型
         */
        void onConnected(NetworkType networkType);
    }

    public static final class WifiScanResults {

        private List<ScanResult> allResults = new ArrayList<>();
        private List<ScanResult> filterResults = new ArrayList<>();

        public WifiScanResults() {
        }

        public List<ScanResult> getAllResults() {
            return allResults;
        }

        public List<ScanResult> getFilterResults() {
            return filterResults;
        }

        public void setAllResults(List<ScanResult> allResults) {
            this.allResults = allResults;
            filterResults = filterScanResult(allResults);
        }

        private static List<ScanResult> filterScanResult(final List<ScanResult> results) {
            if (results == null || results.isEmpty()) {
                return new ArrayList<>();
            }
            LinkedHashMap<String, ScanResult> map = new LinkedHashMap<>(results.size());
            for (ScanResult result : results) {
                if (StringUtils.isEmpty(result.SSID)) {
                    continue;
                }
                ScanResult resultInMap = map.get(result.SSID);
                if (resultInMap != null && resultInMap.level >= result.level) {
                    continue;
                }
                map.put(result.SSID, result);
            }
            return new ArrayList<>(map.values());
        }

    }
}